热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

剩余|可能会_android内存管理

篇首语:本文由编程笔记#小编为大家整理,主要介绍了android内存管理相关的知识,希望对你有一定的参考价值。虚拟内存

篇首语:本文由编程笔记#小编为大家整理,主要介绍了android内存管理相关的知识,希望对你有一定的参考价值。



虚拟内存

如果在程序被挂起或被换出前仅仅使用了一部分进程快,那么为该进程给内存中装入太多的块显然会带来巨大的浪费。而虚拟内存借助磁盘和内存交换,仅仅装入这小部分块来更好地使用内存,然后,如果程序转移到或访问到不在内存中的某个快中的指令或数据时,就会引发一个中断,告诉操作系统读取需要的块。
我们知道进程中的所有内存访问都是逻辑地址,这些逻辑地址在运行时动态的被转换成物理地址,而这意味着一个进程可以被换入或换出内存,使得进程可以在执行过程中的不同时刻占据内存中的不同区域。还有一个进程可以划分成许多块,在执行过程中,这些块不需要连续的位于内存中。这两点也是虚拟内存突破所在。

接下来看虚拟内存重要的一些概念和组成


  • 页号:每个进程内存都可以分割成好多块,每一块有对应的页号标记。
  • 页框号:正真物理内存也相当于被分成N多块,每一块对应其中一个页框号
  • 页表项:它通常由页号是否存在内存标记,控制位,页框号等组成,是一个检索页号对应的页框号,每一个页对应一个页表项。
  • 页表:有多个页表项组成,当启动进程时,该页表会加载到内存,并通过一些策略管理页表项。
  • 虚拟地址(逻辑地址):页号,偏移量
  • 物理地址:页框号,偏移量,就是内存实际地址
  • 转换检测缓冲区(TLB):它是位于CPU和内存之间的一个高速缓存存储器,用来存储访问过的页表项,避免了每个虚拟内存访问引起了两次物理内存访问(一次页表项,另一次读取需要的数据)。
  • 辅存:就是物理磁盘之类存储外介

其虚拟内存分页地址转换过程如图:

其转换检测缓冲区的使用:

虚拟内存整个操作流程:


内存的分配层

Native层:本地层的程序基本上是由c/c++编写的,与开发人员直接相关的内存函数包括malloc/free,new/delete等,这部分不受Java Object Head的大小限制,但它会受到系统的限制,这块动态分配的内存需要人工管理,容易出现内存致命Bug,因此谷歌应用了智能指针来尽量避免这块,如果开发人员过多使用Native层去分配内存的话,可能会引发系统采取激进的方式杀掉某些进程。
Java层(Java Object Head):它的最大值就是指我们android应用程序能够使用的最大内存,它通常是通过new,或者加载图像(External Memory也叫Bitmap Memory)分配内存,可以通过-Xms和-Xmx选项来指定Java Object Heap的最小值和最大值,但相对于Native层,其分配的内存不需要人工去管理释放。


进程间通信——mmap

一般来说,如果进程间通讯不通过内存映射共享的话,每次的进程通讯会涉及到四次的复制操作,并且这四次进程复制操作是发生在内核和进程两者之间,而不是发生内核或进程两者之间,这中复制操作开销相对于后两者复制操作来说较大,其进程通讯过程如图:

从图中来看,四次过程,分贝是两次read(),write()方法,首先server从文件读取数据到进程空间中,再从进程写到内核通讯通道中,然后client再从该内核通道中读取数据到它的进程空间中,最后才写到输出文件中。
其mmap可以将某个设备或者文件映射到应用进程的内存空间中,这样访问这块内存就相当于对设备/文件进行读写,而不需要再通过两次的read()和两次的write()了,而仅仅需要一次read()和一次write(),因此Android经常通过mmap用于进程间通信,即通过映射同一块物理内存来共享内存,其整个过程如图:

关于mmap性能及详细细节,请参考以下这篇博客,这篇博客有提到过:
http://blog.csdn.net/xuguoli_beyondboy/article/details/45085703


Low Memory Killer

当运行的程序超过一定数量,或者涉及复杂的计算时,很可能出现内存不足,进而导致系统卡顿的现象,在Android中,用户如果启动的应用程序(如Activity Finish()),该进程不是马上被清理,而是驻留在内存中,这样加快再次启动的速度,但增加了内存开销。
android在管理内存不足时,主要通过lowmem_adj和lowmem_minfree去判断,其中lowmen_adj和lowmem_minfreed一一对应,其中adj的取值范围是-17~15,数字越小表示进程级别越高(通常只有0~15被使用)。
源码分析:
Lowmemorykiller.c

//默认定义了4个
static int lowmem_adj[6] =
0,
1,
6,
12,
;
//这是默认一页为4kB
static int lowmem_adj_size = 4;
//默认定义了4个
//lowmem_minfree[i]*lowmem_adj_size=nMB
static size_t lowmem_minfree[6] =
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
;

第一个数组lowmen_adj最多有6个元素(默认只定义了4个),它adj值对应是否杀掉该层级进程的内存容量临界值,第二个数组对应”层级”的描述,如:
lowmen_minfree的第一个元素时3*512,即3*512*lowmen_adj_size=6MB,其对应的adj的值为0,如果Android系统可用内存小于6MB时,adj值为0以下的那些进程就会被清理。
当我们可以下面两个文件去修改,如:
/sys/module/lowmemorykiller/parameters/adj 0,8
/sys/module/lowmemorykiller/parameters/minfree 10244096

当系统的内存剩余空间于1024*4~4096*4之间,oom_adj大于或等于8的进程将会被杀掉。
源码分析:

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask);
//当内存较低且需要释放时,会初始化shrinker
static struct shrinker lowmem_shrinker =
.shrink = lowmem_shrink,
.seeks = DEFAULT_SEEKS * 16
;
static int __init lowmem_init(void)
register_shrinker(&lowmem_shrinker);
return 0;
static void __exit lowmem_exit(void)
unregister_shrinker(&lowmem_shrinker);
module_init(lowmem_init);
module_exit(lowmem_exit);
static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
struct task_struct *p;
struct task_struct *selected = NULL;
int rem = 0;
int tasksize;
int i;
int min_adj = OOM_ADJUST_MAX + 1;
int selected_tasksize = 0;
int selected_oom_adj;
//获取lowmen_adj的数组长度
int array_size = ARRAY_SIZE(lowmem_adj);
//剩余内存
int other_free = global_page_state(NR_FREE_PAGES);
//获取剩余分页
int other_file = global_page_state(NR_FILE_PAGES);
// 获取较小的那个长度的值
if (lowmem_adj_size array_size = lowmem_adj_size;
if (lowmem_minfree_size array_size = lowmem_minfree_size;
//查找小于内存临界值的最小min_adj
for (i = 0; i if (other_free other_file min_adj = lowmem_adj[i];
break;


if (nr_to_scan > 0)
lowmem_print(3, "lowmem_shrink %d, %x, ofree %d %d, ma %d\\n",
nr_to_scan, gfp_mask, other_free, other_file,
min_adj);
rem = global_page_state(NR_ACTIVE_ANON) +
global_page_state(NR_ACTIVE_FILE) +
global_page_state(NR_INACTIVE_ANON) +
global_page_state(NR_INACTIVE_FILE);
if (nr_to_scan <&#61; 0 || min_adj &#61;&#61; OOM_ADJUST_MAX &#43; 1)
lowmem_print(5, "lowmem_shrink %d, %x, return %d\\n",
nr_to_scan, gfp_mask, rem);
return rem;

selected_oom_adj &#61; min_adj;
//获取锁
read_lock(&tasklist_lock);
//寻找被杀死进程的处理
for_each_process(p)
struct mm_struct *mm;
int oom_adj;
task_lock(p);
mm &#61; p->mm;
if (!mm)
task_unlock(p);
continue;

oom_adj &#61; mm->oom_adj;
//如果oom_adj小于min_adj就不杀死
if (oom_adj task_unlock(p);
continue;

//该进程的内存占用大小
tasksize &#61; get_mm_rss(mm);
task_unlock(p);
//内存占用大小不超过0&#xff0c;该进程不被杀死
if (tasksize <&#61; 0)
continue;
//如果当前的进程比前一个刚被杀死的进程的oom_adj或分配内存大小要小&#xff0c;则该进程不被杀死
if (selected)
if (oom_adj continue;
if (oom_adj &#61;&#61; selected_oom_adj &&
tasksize <&#61; selected_tasksize)
continue;
//选择当前进程为该被杀死的进程
selected &#61; p;
selected_tasksize &#61; tasksize;
selected_oom_adj &#61; oom_adj;
//输出进程id标识&#xff0c;进程描述&#xff0c;adj值&#xff0c;进程内存占用大小
lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\\n",
p->pid, p->comm, oom_adj, tasksize);

//杀死该进程
if (selected)
lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\\n",
selected->pid, selected->comm,
selected_oom_adj, selected_tasksize);
force_sig(SIGKILL, selected);
rem -&#61; selected_tasksize;

lowmem_print(4, "lowmem_shrink %d, %x, return %d\\n",
nr_to_scan, gfp_mask, rem);
//释放锁
read_unlock(&tasklist_lock);
return rem;

进程可以通过两种方法来改变自己的adj
修改/proc//oom_adj 的值&#xff0c;如在init.rc&#xff1a;
on early-init
# Set init and its forked children&#39;s oom_adj.
write /proc/1/oom_adj -16

那么这样保证PID为1的进程不会被杀死。
在AndroidManifest.xml文件中给application标签添加”android&#xff1a;persistent&#61;true”属性来确保该进程不被杀死。


进程和oom_adj之间关系

进程可以规划为几块&#xff1a;前台进程&#xff0c;可见进程&#xff0c;服务进程&#xff0c;后台进程&#xff0c;空进程。
它们所属的adj值如图所示&#xff1a;

参考资料&#xff1a;
http://blog.csdn.net/luoshengyang/article/details/8852432
http://www.programering.com/a/MjNzADMwATE.html
http://www.kohala.com/start/unpv22e/unpv22e.chap12.pdf


推荐阅读
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • 达人评测 酷睿i5 12450h和锐龙r7 5800h选哪个好 i512450h和r75800h对比
    本文介绍了达人评测酷睿i5 12450h和锐龙r7 5800h选哪个好的相关知识,包括两者的基本配置和重要考虑点。希望对你在选择时提供一定的参考价值。 ... [详细]
  • 这个问题困扰了我两天,卸载Dr.COM客户端(我们学校上网要装这个客户端登陆服务器,以后只能在网页里输入用户名和密码了),问题解决了。问题的现象:在实验室机台式机上安装openfire和sp ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
  • Question该提问来源于开源项目:react-native-device-info/react-native-device-info ... [详细]
  • mysqldinitializeconsole失败_mysql03误删除了所有用户解决办法
    误删除了所有用户解决办法第一种方法(企业常用)1.将数据库down掉[rootdb03mysql]#etcinit.dmysqldstopShuttingdownMySQL..SU ... [详细]
  • 近来有一个需求,是需要在androidjava基础库中插入一些log信息,完成这个工作需要的前置条件有编译好的android源码具体android源码如何编译,这 ... [详细]
author-avatar
fishdenise_496
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有